package com.osce.score.service.socket;

import cn.hutool.core.date.DateField;
import cn.hutool.core.date.DateTime;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.csce.domain.entity.score.ScoreExExam;
import com.csce.domain.entity.score.ScoreExExamPlace;
import com.csce.domain.entity.score.ScoreExExamPlaceAllot;
import com.csce.domain.entity.score.ScoreSheet;
import com.csce.domain.entity.score.socket.Connect;
import com.csce.domain.entity.score.socket.WaitingDataVo;
import com.csce.domain.entity.score.vo.ExamAllotResult;
import com.doudou.core.container.ListUtil;
import com.doudou.core.str.StrUtil;
import com.doudou.core.date.DateUtil;
import com.osce.common.constant.CacheConstants;
import com.osce.common.core.redis.RedisCache;
import com.osce.interfaces.score.IScoreExExamPlaceService;
import com.osce.interfaces.score.IScoreExExamService;
import com.osce.interfaces.score.IScoreSheetService;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * @author zzxjs
 * @since 2023/12/20
 * 线下考试，redis相关操作服务类
 */
@Service
public class ExamRedisService {

    @Resource
    private IScoreExExamService examService;
    @Resource
    private IScoreExExamPlaceService placeService;
    @Resource
    private IScoreSheetService sheetService;
    @Resource
    private RedisCache redisCache;


    /**
     * 创建socket使用的考站模型
     * 1、排站完成后去创建redis的考站展示模型
     * 1.1、先删除之前创建的考站模型
     * 1.2、创建新的考站模型
     * 2、考试新增最早开始时间，新增或修改的时候计算
     * 3、查询列表的时候判断当前时间是否是最早开始时间-2小时之后，如果是，修改开考状态，前端展示用户无法修改
     * 4、删除的考试的时候一起删掉对应的考试模板
     * 5、填充redis的时候计算过期时间，到考试当天的后一天删除，减轻redis压力
     */
    public void buildExamSocketModel(Long examId) {
        //先清空
        ExamAllotResult result = examService.selectAllot(examId);
        ScoreExExam scoreExExam = examService.selectScoreExExamById(examId);
        clearExamSocketModel(scoreExExam);
        Map<Long, ScoreExExamPlace> placeMap = placeService.selectBasicExPlaceToMapByIds(result.placeIdArr());
        //遍历
        result.forEach((exDate, timeDatas) -> {//第一层 日期->时间分段map
            LinkedHashMap<String, JSONObject> dataMap = new LinkedHashMap<>();//候考数据map
            HashMap<String, Connect> connectMap = new HashMap<>();//考站连接对象map
            timeDatas.forEach((timeId, placeDatas) -> {//第二层 时间分段id->考站map
                //创建候考厅数据模型
                WaitingDataVo waitingDataVo = new WaitingDataVo(examId, timeId + "");
                waitingDataVo.setNameShow(StrUtil.equals("1", scoreExExam.getNameShow()));
                //为候考厅添加各个考站的模型
                placeDatas.forEach((placeId, placeLists) -> {//第三层 考站id->考站list
                    ScoreExExamPlace exPlace = placeMap.get(placeId);
                    //查询该考站下的所有评分表
                    Map<Long, ScoreSheet> sheetMap = sheetService.selectSheetMapBySkillId(exPlace.getSkillId());
                    //添加考站模型
                    for (int index = 0; index < placeLists.size(); index++) {
                        //添加候考数据
                        List<ScoreExExamPlaceAllot> examPlaceAllots = placeLists.get(index);
                        waitingDataVo.addPlaceWaitingModel(placeId + "", exPlace.getPlaceId(), exPlace.getPlaceName(), index, exPlace.getWaitNum(), examPlaceAllots, sheetMap);
                        //添加考站连接对象
                        Connect connect = new Connect(examId, timeId + "", placeId + "", index);
                        connectMap.put(connect.getUnique(), connect);
                    }
                });
                dataMap.put(StrUtil.joinSep("-", examId, timeId), JSONUtil.parseObj(waitingDataVo));
            });
            setExamSocketWaitingModel(examId + "", exDate, dataMap, connectMap);//填充进redis
        });
    }

    /**
     * 填充考试的socket使用的考站模型
     */
    private void setExamSocketWaitingModel(String examId, String date, LinkedHashMap<String, JSONObject> dataVoMap, Map<String, Connect> connectMap) {
        String waitingKey = getExamSocketWaitingModelKey(examId, date);
        String connectKey = getExamSocketConnectModelKey(date);
        redisCache.setCacheMap(waitingKey, dataVoMap);
        redisCache.setCacheMap(connectKey, connectMap);
        //计算过期时间
        DateTime exDate = DateTime.of(date, DateUtil.yyyyMMdd);
        exDate = exDate.offset(DateField.DAY_OF_MONTH, 1);//加一天
        long expire = exDate.getTime() - DateTime.now().getTime();
        //设置过期时间
        redisCache.expire(waitingKey, expire, TimeUnit.MILLISECONDS);
        redisCache.expire(connectKey, expire, TimeUnit.MILLISECONDS);
    }

    /**
     * 清空该考试的socket使用的考站模型
     */
    public void clearExamSocketModel(Long examId) {
        clearExamSocketModel(examService.selectScoreExExamById(examId));
    }

    public void clearExamSocketModel(ScoreExExam scoreExExam) {
        Long examId = scoreExExam.getId();
        redisCache.deleteByPre(CacheConstants.SCORE_EXAM_WAITING_DATA_DEL_MAP_KEY + examId);
        //查询该考试的时间列表
        for (DateTime examDate : DateUtil.rangeToList(scoreExExam.getExStartDate(), scoreExExam.getExEndDate(), DateField.DAY_OF_MONTH)) {//先根据时间查询
            String hashKey = getExamSocketConnectModelKey(examDate.toDateStr());
            Set<String> connectKeys = redisCache.hashKeys(hashKey);
            if (ListUtil.isNotEmpty(connectKeys)) {
                for (String connectKey : connectKeys) {
                    if (connectKey.startsWith(examId + "-")) {//如果第一个和考试id一致，删除
                        redisCache.deleteCacheMapValue(hashKey, connectKey);
                    }
                }
            }
        }
    }


    /**
     * 根据考试和日期生成 候考数据redis 的key
     */
    private String getExamSocketWaitingModelKey(String examId, String date) {
        return StrUtil.format(CacheConstants.SCORE_EXAM_WAITING_DATA_ADD_MAP_KEY, examId, date);
    }

    /**
     * 根据考试和日期生成 考站内部连接对象数据redis 的key
     */
    private String getExamSocketConnectModelKey(String date) {
        return StrUtil.format(CacheConstants.SCORE_EXAM_PLACE_CONNECT_MAP_KEY + date);
    }

    /**
     * 获取候考数据
     */
    public WaitingDataVo getCacheWaitingDataVo(String examId, String timeId) {
        JSONObject value = redisCache.getCacheMapValue(getExamSocketWaitingModelKey(examId, today()), StrUtil.joinSep("-", examId, timeId));
        return value.toBean(WaitingDataVo.class).orderUnScheduleds();
    }

    /**
     * 填充候考学员数据
     */
    public void setCacheWaitingDataVo(String examId, String timeId, WaitingDataVo dataVo) {
        redisCache.setCacheMapValue(getExamSocketWaitingModelKey(examId, today()), examId + "-" + timeId, JSONUtil.parseObj(dataVo));
    }


    /**
     * 获取考站连接数据
     */
    public Connect getCacheExPlaceConnect(String connectUnique) {
        return redisCache.getCacheMapValue(getExamSocketConnectModelKey(today()), connectUnique);
    }

    /**
     * 获取考站连接数据 map
     */
    public Map<String, Connect> getCacheExPlaceConnectMap() {
        return redisCache.getCacheMap(getExamSocketConnectModelKey(today()));
    }

    /**
     * 填充全部考站连接数据
     */
    public void setCacheExPlaceConnectMap(Map<String, Connect> connectMap) {
        redisCache.setCacheMap(getExamSocketConnectModelKey(today()), connectMap);
    }

    /**
     * 填充考站连接数据
     */
    public void setCacheExPlaceConnect(Connect connect) {
        redisCache.setCacheMapValue(getExamSocketConnectModelKey(today()), connect.getUnique(), connect);
    }

    private String today() {
        return DateUtil.today();
    }
}
